package edu.hawaii.halealohacli.kmj;

import java.io.IOException;
import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;
import javax.xml.datatype.XMLGregorianCalendar;
import org.wattdepot.client.BadXmlException;
import org.wattdepot.client.MiscClientException;
import org.wattdepot.client.NotAuthorizedException;
import org.wattdepot.client.ResourceNotFoundException;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.util.tstamp.Tstamp;

/**
 * Implements the monitor-goal command which checks if the current power meets the specified power
 * reduction goal (in percent, defaults to 5%) at a specified interval in seconds (defaults to 10
 * seconds).
 * 
 * @author Christopher Foo
 * 
 */
public class MonitorGoal implements Command {

  /**
   * The desired format for decimal numbers.
   */
  private final DecimalFormat format = new DecimalFormat("###.00");
  
  /**
   * The baseline object containing the baseline data to compare against.
   */
  private Baseline baseline;

  /**
   * The name of the source to monitor.
   */
  private String source;

  /**
   * The interval in seconds that the source should be monitored at.
   */
  private int interval;

  /**
   * A WattDepotClient connected to the WattDepot server to perform the queries.
   */
  private WattDepotClient client;

  /**
   * The reduction goal in percent.
   */
  private int goal;

  /**
   * If the command should stop monitoring the goal.
   */
  private boolean stop;
  /**
   * Creates a new MonitorGoal object with the specified properties.
   * 
   * @param client The WattDepotClient connected to the WattDepot server to query.
   * @param source The name of the source to monitor.
   * @param goal The goal power reduction in percent.
   * @param interval The interval in seconds that the source should be monitored at.
   * @param baseline The Baseline object containing the baseline data that the current power should
   * be compared to.
   */
  MonitorGoal(WattDepotClient client, String source, int goal, int interval, Baseline baseline) {
    this.baseline = baseline;
    this.client = client;
    this.source = source;
    this.goal = goal;
    this.interval = interval;
    this.stop = false;
  }

  /**
   * Get the Baseline object.
   * 
   * @return The Baseline object containing the data to compare the current power against.
   */
  public Baseline getBaseline() {
    return this.baseline;
  }

  /**
   * Get the name of the source to monitor.
   * 
   * @return The name of the source to monitor.
   */
  public String getSource() {
    return this.source;
  }

  /**
   * Get the interval in seconds at which to monitor the source.
   * 
   * @return The interval in seconds at which to monitor the source.
   */
  public int getInterval() {
    return this.interval;
  }

  /**
   * Gets the WattDepotClient used to query the WattDepot server.
   * 
   * @return The WattDepotClient used to query the WattDepot server.
   */
  public WattDepotClient getClient() {
    return this.client;
  }

  /**
   * Gets the power reduction goal in percent.
   * 
   * @return The power reduction goal in percent.
   */
  public int getGoal() {
    return this.goal;
  }

  /**
   * Sets the value of stop which determines whether the command should stop monitoring or not.
   * @param stop If the command should stop monitoring.
   */
  public void setStop(boolean stop) {
    this.stop = stop;
  }
  /**
   * Gets the format for decimal numbers.
   * @return The format for decimal numbers.
   */
  public DecimalFormat getFormat() {
    return (DecimalFormat) this.format.clone();
  }

  @Override
  /**
   * Runs the MonitorGoal command.
   */
  public void printResult() {
    Timer timer = new Timer();
    TimerTask task = new MonitorGoalTask(this);
    
    System.out.println("Monitoring the goal.  Press [Enter] to stop monitoring.\n");
    timer.scheduleAtFixedRate(task, 0, this.interval * 1000);

    // While no user input
    while (!stop) {

      // If user input, stop waiting.
      try {

        if (System.in.available() != 0) {

          // Remove input on the System.in buffer.
          while (System.in.available() != 0) {
            System.in.read();
          }

          this.stop = true;
        }
      }
      catch (IOException e) {
        System.out.println("Error: IOException when waiting for user input.");
      }
      // Otherwise, let the timer run scheduled task
    }

    // User entered something to exit the while loop, stop monitoring
    timer.cancel();
  }
}

/**
 * The TimerTask which checks if the goal has been met or not and prints the results to the screen.
 * 
 * @author Christopher Foo
 * 
 */
class MonitorGoalTask extends TimerTask {

  /**
   * The MonitorGoal command that is running this task.
   */
  MonitorGoal command;

  /**
   * Creates a new MonitorGoalTask for the specified command.
   * 
   * @param command The MonitorGoal command running this task.
   */
  MonitorGoalTask(MonitorGoal command) {
    this.command = command;
  }

  @Override
  /**
   * Checks if the goal has been met and prints the results to the screen.
   */
  public void run() {
    System.out.println(this.generateResultString());
  }

  /**
   * Gets the current power being consumed by the source in kW.
   * 
   * @return The current power being consumed by the source in kW.
   * @throws NotAuthorizedException If the client is not authorized to access the server.
   * @throws ResourceNotFoundException If the resource cannot be found.
   * @throws BadXmlException If bad XML was returned.
   * @throws MiscClientException If a miscellaneous error occurred.
   */
  public double getCurrentPower() throws NotAuthorizedException, ResourceNotFoundException,
      BadXmlException, MiscClientException {
    return this.command.getClient().getLatestPowerConsumed(this.command.getSource()) / 1000;
  }

  /**
   * Creates the string to be printed by the MonitorGoal command.
   * 
   * @return The string to be printed by the MonitorGoal command.
   */
  public String generateResultString() {

    String resultString = "";

    // Get timestamp and print
    XMLGregorianCalendar stamp = Tstamp.makeTimestamp();
    String timestamp =
        stamp.getYear() + "-" + this.adjustInt(stamp.getMonth()) + "-"
            + this.adjustInt(stamp.getDay()) + " " + this.adjustInt(stamp.getHour()) + ":"
            + this.adjustInt(stamp.getMinute()) + ":" + this.adjustInt(stamp.getSecond());
    resultString += timestamp + "\t";

    // Get current power and compare to goal
    double currentPower;
    try {
      currentPower = this.getCurrentPower();
      resultString += this.command.getFormat().format(currentPower) + " kW\t";
      if (meetsGoal(currentPower)) {
        resultString += "Goal Met!\n";
      }
      else {
        resultString += "Goal Not Met!\n";
      }
    }
    catch (NotAuthorizedException e) {
      resultString = e.getMessage();
    }
    catch (ResourceNotFoundException e) {
      resultString = e.getMessage();
    }
    catch (BadXmlException e) {
      resultString = e.getMessage();
    }
    catch (MiscClientException e) {
      resultString = e.getMessage();
    }
    return resultString;
  }

  /**
   * Checks whether the given current power value meets the goal.
   * 
   * @param currentPower The current power in kW.
   * @return If the current power meets the goal.
   */
  public boolean meetsGoal(double currentPower) {
    Calendar calendar = Calendar.getInstance();
    double baselineValue =
        command.getBaseline().getBaselineValues()[calendar.get(Calendar.HOUR_OF_DAY)];
    return 100 - command.getGoal() > (currentPower / baselineValue) * 100;
  }

  /**
   * Adjusts the given integer value so that it is always at least two characters long.  Expected
   * values: 0 <= value <= 99
   * @param value The value to be adjusted.
   * @return A string representation of the adjusted value.
   */
  public String adjustInt(int value) {
    if (value >= 0 && value <= 9) {
      return "0" + value;
    }
    else {
      return String.valueOf(value);
    }
  }
}